package com.okaixz.client.service.impl;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.okaixz.client.controller.WebSocketServer;
import com.okaixz.client.mapper.UserMapper;
import com.okaixz.client.model.domain.OrderInfo;
import com.okaixz.client.model.domain.User;
import com.okaixz.client.model.dto.*;
import com.okaixz.client.model.vo.GetOpenIdResult;
import com.okaixz.client.model.vo.WxAppPayResult;
import com.okaixz.client.properties.WeChatProperties;
import com.okaixz.client.properties.WxProperties;
import com.okaixz.client.service.IOrderInfoService;
import com.okaixz.client.service.ITokenService;
import com.okaixz.client.service.IUserService;
import com.okaixz.client.service.IWxService;
import com.okaixz.client.wechat.WxAppPayService;
import com.okaixz.common.client.WsResult;
import com.okaixz.common.client.exception.BusinessException;
import com.okaixz.common.client.exception.CommonErrorCode;
import com.okaixz.common.client.utils.HttpClientUtil;
import com.okaixz.common.client.utils.ThreadPoolUtils;
import com.okaixz.common.client.utils.WxLoginUtil;
import com.okaixz.common.client.vo.GetAccessTokenResult;
import com.okaixz.common.client.vo.WXUserInfo;
import com.okaixz.common.client.vo.WxAppPayParams;
import com.okaixz.common.client.vo.WxMiniProgramPayClientReqeust;
import com.okaixz.common.constant.CommonConstants;
import com.okaixz.common.constant.RedisConstants;
import com.okaixz.common.constant.WsResultCode;
import com.okaixz.common.core.redis.RedisCache;
import com.okaixz.common.utils.StringUtils;
import com.okaixz.manager.utils.WxPayUtil;
import com.wechat.pay.java.service.partnerpayments.nativepay.model.Transaction;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * APP用户信息表 服务实现类
 * </p>
 *
 * @author cdz
 * @since 2024-10-08
 */
@Service
@Slf4j
public class WxServiceImpl extends ServiceImpl<UserMapper, User> implements IWxService {
    @Autowired
    private ITokenService tokenService;
    @Autowired
    private IOrderInfoService orderInfoService;
    @Autowired
    private IUserService userService;
    @Autowired
    private RedisCache redisCache;
    @Autowired
    private WxLoginUtil wxLoginUtil;
    @Autowired
    private WxPayUtil wxPayUtil;

    @Autowired
    private WxProperties wxProperties;
    @Autowired
    private WeChatProperties weChatProperties;

    @Autowired
    private WxAppPayService wxAppPayService;

    @Override
    public UserDto processWxQrcodeLoginCallBack(String code, String state) {
        GetAccessTokenResult accessTokenResult = wxLoginUtil.getAccessToken(code);
        if (accessTokenResult == null) {
            return null;
        }
        if (accessTokenResult.getErrcode() != null) {
            return null;
        }
        String access_token = accessTokenResult.getAccess_token();
        String openid = accessTokenResult.getOpenid();
        String refresh_token = accessTokenResult.getRefresh_token();

        WXUserInfo userInfo = wxLoginUtil.getUserInfo(access_token, openid);
        if (userInfo == null) {
            return null;
        }
        log.info(userInfo.toString());

        User user = userService.processWxQrcodeLoginCallBack(openid, access_token, refresh_token, userInfo.getHeadimgurl(), userInfo.getNickname());

        UserDto userDto = tokenService.refreshUserAndToken(user.getUserId());


        return userDto;
    }

    /**
     * 获取微信登录二维码
     *
     * @param sid
     * @return
     */
    @Override
    public String getQrcode(String sid) {
        return wxLoginUtil.getQrcode(sid);
    }

    /**
     * 获取微信支付二维码
     *
     * @return
     */
    @Override
    public String getWxPayCode(String userId, Integer gid, String sessionId) {
        OrderInfo orderInfo = orderInfoService.createOrderAndSaveInCache(userId, "",gid);
        if (orderInfo == null) {
            return null;
        }

        Long orderId = orderInfo.getId();
        Double price = orderInfo.getGprice() * 100f + 0.5;
        log.info("price:{}", orderInfo.getGprice());
        log.info("price:{}", price);
        String payUrl = wxPayUtil.getPayUrl(orderId + "", userId, gid, sessionId, price.intValue(), orderInfo.getGname());
        return payUrl;
    }
    @Override
    @Transactional
    public WxAppPayParams wxAppCreateOrder(String userId, Integer gid, String sessionId) {
        User user = userService.getUserFormRedisOrDb(userId);
        if (user == null) {
            throw new BusinessException(CommonErrorCode.USER_NOT_EXIST);
        }
        String clientName = user.getClientName();
        OrderInfo orderInfo = orderInfoService.createOrderAndSaveInCache(userId, clientName, gid);

        Long orderId = orderInfo.getId();
        Double price = orderInfo.getGprice() * 100f + 0.5;
        log.info("price:{}", orderInfo.getGprice());
        log.info("price:{}", price);
        try {
//            String sessionId, String userId,String orderNo, Integer gid, Integer price, String gname
            WxAppPayParams payParams = wxAppPayService.createPrePayParams(sessionId,
                    userId,
                    orderId + "",
                    orderInfo.getGid(),
                    price.intValue(),
                    orderInfo.getGname(),clientName);
            return payParams;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public WxMiniProgramPayClientReqeust wxMiniProgramCreateOrder(String userId, Integer gid, String sessionId) {
        User user = userService.getUserFormRedisOrDb(userId);
        OrderInfo orderInfo = orderInfoService.createOrderAndSaveInCache(userId,user.getClientName(), gid);
        if (orderInfo == null) {
            return null;
        }

        Long orderId = orderInfo.getId();
        Double price = orderInfo.getGprice() * 100f + 0.5;
        log.info("price:{}", orderInfo.getGprice());
        log.info("price:{}", price);
        WxMiniProgramPayClientReqeust result = null;
        try {
            result = wxPayUtil.getMinProgramPayRequest(sessionId,
                    userId,
                    user.getWxOpenid(),
                    orderId + "",
                    orderInfo.getGid(),
                    price.intValue(),
                    orderInfo.getGname());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 处理微信支付回调
     *
     * @param request
     * @param response
     * @return
     */
    @Override
    public String processNativePayCallback(HttpServletRequest request, HttpServletResponse response) {
        WxPayUtil.WxPayResult wxPayResult = wxPayUtil.processResult(request, response);
        String result = wxPayResult.getResult();
        String sessionId = null;
        Transaction transaction = wxPayResult.getTransaction();
        if (transaction == null) {
            return result;
        }
        Transaction.TradeStateEnum tradeState = transaction.getTradeState();
        int payStatus = CommonConstants.PAY_STATUS_NOTPAY;
        if (Transaction.TradeStateEnum.SUCCESS == tradeState) {
            //支付成功
            payStatus = CommonConstants.PAY_STATUS_PAY_SUCCESS;
        } else {
            //支付失败
            payStatus = CommonConstants.PAY_STATUS_PAY_FAIL;
        }

        String outTradeNo = transaction.getOutTradeNo();
        if (StringUtils.isEmpty(outTradeNo)) {
            return result;
        }
        String attach = transaction.getAttach();
        WxPayAttachInfo attachInfo = null;
        if (StringUtils.isNotEmpty(attach)) {
            try {
                attachInfo = JSON.parseObject(attach, WxPayAttachInfo.class);
                sessionId = attachInfo.getC();
            } catch (Exception e) {
                e.printStackTrace();
            }
            //            String userId=attach.substring(0,21);
            //            String gid=attach.substring(21);
        }


        //从缓存中取订单信息
        OrderInfo orderInfo = orderInfoService.getCacheOrder(outTradeNo);

        if (orderInfo == null) {
            if (attachInfo == null) {
                // TODO: 2024/10/25 缓存中没有订单信息 附加信息又为空 将支付记录到异常表中
                return result;
            }
            //缓存中没有订单信息 重新创建一个订单信息
            String userId = attachInfo.getA();
            Integer gid = attachInfo.getB();
            log.info("processNativePayCallback userId={}", userId);
            log.info("gid={}", gid);
            log.info("sessionId={}", sessionId);
            orderInfo = orderInfoService.createOrder(userId, gid);
        }
        orderInfo.setPayType(CommonConstants.PAY_TYPE_WXPAY);
        orderInfo.setPayState(payStatus);
        String successTime = transaction.getSuccessTime();
        if (StringUtils.isNotEmpty(successTime)) {
            try {

//                // 使用 DateTimeFormatter 解析时间字符串
//                DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
//                OffsetDateTime offsetDateTime = OffsetDateTime.parse(isoTimeString, formatter);
//
//                // 将 OffsetDateTime 转换为 LocalDateTime
//                LocalDateTime localDateTime = offsetDateTime.toLocalDateTime();

                LocalDateTime successDateTime = LocalDateTime.parse(successTime, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
                orderInfo.setPayTime(successDateTime);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        orderInfoService.saveOrUpdate(orderInfo);//保存订单信息到数据库中

        UserDto userDto = userService.updateVipInfo(orderInfo.getGid(), orderInfo.getUserId());//更新数据库中的vip用户信息
        //        userDto=tokenService.refreshUserAndToken(userDto.getUserId());
        if (sessionId != null) {
            userDto.setToken(null);
            WebSocketServer.sendData(sessionId, WsResult.success(userDto, WsResultCode.PAY_SUCCESS));//通过websocket通知支付成功
        }

        return result;
    }
    /**
     * 处理微信app支付回调
     *
     * @param request
     * @param response
     * @return
     */
    @Override
    public String processAppPayCallback(HttpServletRequest request, HttpServletResponse response) {
        WxAppPayResult wxAppPayResult = wxAppPayService.processResult(request, response);
        String result = wxAppPayResult.getResult();

        ThreadPoolUtils.getInstances().createNativePool().execute(() -> {
            processOrderIfno(wxAppPayResult);
        });
        return result;
    }

    public void processOrderIfno(WxAppPayResult wxAppPayResult){
        String sessionId = null;
        WxPayAttachInfo attachInfo = wxAppPayResult.getAttachInfo();
        if (attachInfo != null) {
            sessionId=attachInfo.getC();
        }
        UserDto userDto = orderInfoService.processWxAppPayResult(wxAppPayResult);
        if (sessionId != null) {
            userDto.setToken(null);
            WebSocketServer.sendData(sessionId, WsResult.success(userDto, WsResultCode.PAY_SUCCESS));//通过websocket通知支付结果 userDto=null为支付失败 否则成功
        }
    }

    @Override
    public String getAccessToken() {
        String accessToken = getAccessTokenFromRedis();
        if (StringUtils.isNotEmpty(accessToken)) {
            log.info("缓存中取到accessToken={}", accessToken);
            return accessToken;
        }
        try {
            //从微信获取accesstoken
            String url = wxProperties.getGetTokenUrl();
            Map<String, String> paramMap = new HashMap<>();
            paramMap.put("grant_type", "client_credential");
            paramMap.put("appid", wxProperties.getAppid());
            paramMap.put("secret", wxProperties.getSecret());
            String s = HttpClientUtil.doGet(url, paramMap);
            log.info("access_token_result={}", s);
            AccessTokenResult result = JSON.parseObject(s, AccessTokenResult.class);
            accessToken = result.getAccess_token();
            if (result != null && accessToken != null) {
                saveAccessTokenToRedis(accessToken, result.getExpires_in() - 60);//保存到缓存
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return accessToken;
    }
    @Override
    public WxAccessTokenResult getAccessToken(GetAccessTokenParams params) {
        try {
            String code = params.getCode();
            String clientId = params.getClientName();
            //从微信获取accesstoken
            String url = weChatProperties.getGetTokenUrl();

            String appid=null;
            String appSecret = null;

            WeChatProperties.WxAppProperites wxAppProperites = weChatProperties.getWxAppInfo().get(clientId);
            if (wxAppProperites == null) {
                return null;
            }
            appid=wxAppProperites.getAppid();
            appSecret=wxAppProperites.getAppSecret();

            Map<String, String> paramMap = new HashMap<>();
            paramMap.put("appid", appid);
            paramMap.put("secret", appSecret);
            paramMap.put("code", code);
            paramMap.put("grant_type", "authorization_code");
            String s = HttpClientUtil.doGet(url, paramMap);
            log.info("微信开放平台第三登录：access_token_result={}", s);
            WxAccessTokenResult result = JSON.parseObject(s, WxAccessTokenResult.class);
            return result;

            //刷新token
//            paramMap.put("grant_type", "refresh_token");
//            paramMap.put("refresh_token", result.getRefresh_token());
//            String refreshUrl=weChatProperties.getRefreshTokenUrl();
//            String refreshResult = HttpClientUtil.doGet(refreshUrl, paramMap);
//            log.info("微信开放平台刷新token：refreshResult={}", refreshResult);

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public WxUserInfo getUserInfo(String accessToken, String openId) {
        Map<String, String> paramMap = new HashMap<>();
        paramMap.put("access_token", accessToken);
        paramMap.put("openid", openId);
        //从微信获取用户信息
        String url = weChatProperties.getGetUserinfoUrl();
        String s = HttpClientUtil.doGet(url, paramMap);
        log.info("微信开放平台获取用户信息result={}", s);
        WxUserInfo result = JSON.parseObject(s, WxUserInfo.class);
        return result;
    }



    @Override
    public String getQrcodeWithParams(String eventKey, String type) {
        String qrcode = null;
        String accessToken = getAccessToken();
        if (StringUtils.isEmpty(accessToken)) {
            return null;
        }
        String url = wxProperties.getCreateQrcodeUrl() + accessToken;
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("expire_seconds", 3600);//该二维码有效时间，以秒为单位。 最大不超过2592000（即30天），此字段如果不填，则默认有效期为60秒。3600=1小时
        jsonObject.put("action_name", "QR_STR_SCENE");//二维码类型，QR_SCENE为临时的整型参数值，QR_STR_SCENE为临时的字符串参数值，QR_LIMIT_SCENE为永久的整型参数值，QR_LIMIT_STR_SCENE为永久的字符串参数值

        JSONObject scene = new JSONObject();
        scene.put("scene_str", type + CommonConstants.WX_QRCODE_SPLIT + eventKey);//场景值ID（字符串形式的ID），字符串类型，长度限制为1到64

        JSONObject info = new JSONObject();
        info.put("scene", scene);

        jsonObject.put("action_info", info);//二维码详细信息

        try {
            String s = HttpClientUtil.doPostJson(url, JSON.toJSONString(jsonObject));
            log.info("getQrcodeWithParams result:{}", s);
            QrcodeResult result = JSON.parseObject(s, QrcodeResult.class);
            if (result != null) {
                qrcode = result.getUrl();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return qrcode;
    }

    @Override
    public void onMessage(WxMessage wxMessage) {
        if (wxMessage == null) {
            return;
        }
        String msgType = wxMessage.getMsgType();
        if (StringUtils.isEmpty(msgType)) {
            return;
        }
        switch (msgType) {
            case WxMessage.MSG_TYPE_TEXT://文本消息
                processTextMessage(wxMessage);
                break;
            case WxMessage.MSG_TYPE_EVENT://事件消息
                try {
                    processEventMessage(wxMessage);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                break;
        }
    }

    @Override
    public UserDto wxMiniprogramLogin(String code, String shareUserId) {
        GetOpenIdResult openIdResult = getOpenId(code);
        if (openIdResult == null || openIdResult.getOpenid() == null || openIdResult.getSession_key() == null) {
            throw new BusinessException(CommonErrorCode.GET_WX_OPENID_FAIL);
        }
        String openid = openIdResult.getOpenid();
        String session_key = openIdResult.getSession_key();

        return userService.wxMiniprogramLogin(openid, session_key, shareUserId);
    }

    /**
     * 获取微信openId
     *
     * @param code
     * @return
     */
    @Override
    public GetOpenIdResult getOpenId(String code) {
        //        String url = "https://api.weixin.qq.com/sns/jscode2session";
        String url = wxProperties.getMinGetOpenidUrl();
        Map<String, String> map = new HashMap<>();
        map.put("appid", wxProperties.getMinAppid());
        map.put("secret", wxProperties.getMinSecret());
        map.put("js_code", code);
        map.put("grant_type", "authorization_code");
        GetOpenIdResult result = null;
        try {
            String s = HttpClientUtil.doPost(url, map);
            log.info("result={}", s);
            result = JSON.parseObject(s, GetOpenIdResult.class);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }




    private void processTextMessage(WxMessage wxMessage) {
        String content = wxMessage.getContent();
        String fromUserName = wxMessage.getFromUserName();
        log.info("收到文本消息:{}", content);

    }

    /**
     * 处理微信公众号事件消息
     *
     * @param wxMessage
     */
    private void processEventMessage(WxMessage wxMessage) throws Exception {
        String event = wxMessage.getEvent();
        if (StringUtils.isEmpty(event)) {
            return;
        }

        String eventKey = wxMessage.getEventKey();//扫码登录时这是type+sessionId
        eventKey = eventKey.replace("qrscene_", "");
        String openId = wxMessage.getFromUserName();
        int splitIndex = eventKey.indexOf(CommonConstants.WX_QRCODE_SPLIT);
        String type = "";
        String info = "";
        switch (event) {
            case WxMessage.EVENT_SCAN://扫描二维码
                type = eventKey.substring(0, splitIndex);
                info = eventKey.substring(splitIndex + 1);
                log.info("type:{}", type);
                log.info("info:{}", info);
                if (type.equals(CommonConstants.WX_QRCODE_TYPE_LOGIN)) {
                    //扫码登录
                    UserDto userDto = userService.saveOrUpdateUser(openId, CommonConstants.WX_SUBSCRIBE);
                    //已经关注 直接返回登录成功
                    WebSocketServer.sendData(info, WsResult.success(userDto, WsResultCode.LOGIN_SUCCESS));
                }
                break;
            case WxMessage.EVENT_SUBSCRIBE://关注
                type = eventKey.substring(0, splitIndex);
                info = eventKey.substring(splitIndex + 1);
                if (type.equals(CommonConstants.WX_QRCODE_TYPE_LOGIN)) {
                    //扫码登录
                    UserDto userDto = userService.saveOrUpdateUser(openId, CommonConstants.WX_SUBSCRIBE);
                    //已经关注 直接返回登录成功
                    WebSocketServer.sendData(info, WsResult.success(userDto, WsResultCode.LOGIN_SUCCESS));
                }
                log.info("type:{}", type);
                log.info("info:{}", info);
                break;
            case WxMessage.EVENT_UNSUBSCRIBE://取消关注
                userService.saveOrUpdateUser(openId, CommonConstants.WX_UNSUBSCRIBE);
                break;
        }
    }

    private String getAccessTokenFromRedis() {
        String token = null;
        Object cacheObject = redisCache.getCacheObject(RedisConstants.WX_ACCESS_TOKEN);
        if (cacheObject != null) {
            token = (String) cacheObject;
        }
        return token;
    }

    private void saveAccessTokenToRedis(String token, Integer timeoutSeconds) {
        if (StringUtils.isEmpty(token)) {
            return;
        }
        redisCache.setCacheObject(RedisConstants.WX_ACCESS_TOKEN, token, timeoutSeconds, TimeUnit.SECONDS);
    }


}
